from abc import ABC, abstractmethod
from typing import List, Tuple, Optional
import tkinter as tk
from tkinter import ttk, messagebox
import threading

class UI(ABC):
    @abstractmethod
    def display_results(self, expr: str, roots: List[Tuple[Optional[float], Optional[float], Optional[int]]], elapsed_time: float, digits: int):
        pass
    
    @abstractmethod
    def get_input(self, prompt: str, default: str = None) -> str:
        pass

    @abstractmethod
    def run(self):
        pass

class PlainUI(UI):
    def display_results(self, expr: str, roots: List[Tuple[Optional[float], Optional[float], Optional[int]]], elapsed_time: float, digits: int):
        print(f"方程 {expr} 的解:")
        print(f"{'近似解':>12} {'误差':>12} {'迭代次数':>8}")
        for root, fx, it in roots:
            if root is not None:
                print(f"{root:>12.{digits}f} {fx:>12.{digits}e} {it:>8}")
        print(f"计算耗时: {elapsed_time:.3f} 秒")
    
    def get_input(self, prompt: str, default: str = None) -> str:
        return input(prompt) or default
    
    def run(self):
        pass

    @staticmethod
    def is_available() -> bool:
        return True

class RichUI(UI):
    def __init__(self):
        from rich.console import Console
        from rich.table import Table
        self.console = Console()
        self.Table = Table
    
    def display_results(self, expr: str, roots: List[Tuple[Optional[float], Optional[float], Optional[int]]], elapsed_time: float, digits: int):
        table = self.Table(title=f"方程 {expr} 的解")
        table.add_column("近似解", justify="right")
        table.add_column("误差", justify="right")
        table.add_column("迭代次数", justify="right")
        for root, fx, it in roots:
            if root is not None:
                table.add_row(f"{root:.{digits}f}", f"{fx:.{digits}e}", str(it))
        self.console.print(table)
        self.console.print(f"计算耗时: {elapsed_time:.3f} 秒")
    
    def get_input(self, prompt: str, default: str = None) -> str:
        self.console.print(prompt, end="")
        return input() or default
    
    def run(self):
        pass

    @staticmethod
    def is_available() -> bool:
        try:
            import rich
            return True
        except ImportError:
            return False

class TkUI(UI):
    def __init__(self, solver_callback):
        self.root = tk.Tk()
        self.root.title("牛顿法求解器")
        self.solver_callback = solver_callback  # (expr, initial_values, method, multi, precision, digits)
        self.setup_ui()
    
    def setup_ui(self):
        tk.Label(self.root, text="方程表达式 (如 sin(x) - 1, 默认 x**2 - 2):").grid(row=0, column=0, padx=5, pady=5)
        self.expr_entry = tk.Entry(self.root, width=30)
        self.expr_entry.insert(0, "x**2 - 2")
        self.expr_entry.grid(row=0, column=1, columnspan=3, padx=5, pady=5)

        tk.Label(self.root, text="模式:").grid(row=1, column=0, padx=5, pady=5)
        self.mode_var = tk.StringVar(value="r")
        tk.Radiobutton(self.root, text="单初始值", variable=self.mode_var, value="s", command=self.toggle_inputs).grid(row=1, column=1, sticky="w")
        tk.Radiobutton(self.root, text="区间采样", variable=self.mode_var, value="r", command=self.toggle_inputs).grid(row=1, column=2, sticky="w")

        tk.Label(self.root, text="初始值 (默认 1.0):").grid(row=2, column=0, padx=5, pady=5)
        self.x0_entry = tk.Entry(self.root, width=10)
        self.x0_entry.insert(0, "1.0")
        self.x0_entry.grid(row=2, column=1, padx=5, pady=5)

        tk.Label(self.root, text="范围起点 (默认 -10.0):").grid(row=3, column=0, padx=5, pady=5)
        self.start_entry = tk.Entry(self.root, width=10)
        self.start_entry.insert(0, "-10.0")
        self.start_entry.grid(row=3, column=1, padx=5, pady=5)

        tk.Label(self.root, text="范围终点 (默认 10.0):").grid(row=4, column=0, padx=5, pady=5)
        self.end_entry = tk.Entry(self.root, width=10)
        self.end_entry.insert(0, "10.0")
        self.end_entry.grid(row=4, column=1, padx=5, pady=5)

        tk.Label(self.root, text="步长 (默认 1.0):").grid(row=5, column=0, padx=5, pady=5)
        self.step_entry = tk.Entry(self.root, width=10)
        self.step_entry.insert(0, "1.0")
        self.step_entry.grid(row=5, column=1, padx=5, pady=5)

        tk.Label(self.root, text="计算方案:").grid(row=6, column=0, padx=5, pady=5)
        self.method_var = tk.StringVar(value="smart")
        methods = [("智能", "smart"), ("Original", "original"), ("SymPy", "sympy")]
        for i, (text, value) in enumerate(methods):
            tk.Radiobutton(self.root, text=text, variable=self.method_var, value=value, command=self.toggle_precision).grid(row=6, column=i+1, sticky="w")

        tk.Label(self.root, text="多进程:").grid(row=7, column=0, padx=5, pady=5)
        self.multi_var = tk.StringVar(value="smart")
        tk.Radiobutton(self.root, text="智能", variable=self.multi_var, value="smart").grid(row=7, column=1, sticky="w")
        tk.Radiobutton(self.root, text="启用", variable=self.multi_var, value="y").grid(row=7, column=2, sticky="w")
        tk.Radiobutton(self.root, text="禁用", variable=self.multi_var, value="n").grid(row=7, column=3, sticky="w")

        tk.Label(self.root, text="精度 (仅SymPy, 默认 50):").grid(row=8, column=0, padx=5, pady=5)
        self.precision_entry = tk.Entry(self.root, width=10)
        self.precision_entry.insert(0, "50")
        self.precision_entry.grid(row=8, column=1, padx=5, pady=5)

        tk.Label(self.root, text="保留小数位数 (默认 3):").grid(row=9, column=0, padx=5, pady=5)
        self.digits_entry = tk.Entry(self.root, width=10)
        self.digits_entry.insert(0, "3")
        self.digits_entry.grid(row=9, column=1, padx=5, pady=5)

        tk.Button(self.root, text="计算", command=self.compute).grid(row=10, column=0, columnspan=4, pady=10)

        # 修改Treeview列配置，添加stretch=True
        self.tree = ttk.Treeview(self.root, columns=("Root", "Error", "Iterations"), show="headings", height=10)
        self.tree.heading("Root", text="近似解")
        self.tree.heading("Error", text="误差")
        self.tree.heading("Iterations", text="迭代次数")
        self.tree.column("Root", width=100, anchor="e", stretch=True)  # 添加stretch
        self.tree.column("Error", width=100, anchor="e", stretch=True)
        self.tree.column("Iterations", width=80, anchor="e", stretch=True)
        
        # 修改grid布局，添加sticky属性
        self.tree.grid(row=11, column=0, columnspan=4, padx=5, pady=5, sticky="nsew")
        
        # 添加布局权重配置
        self.root.grid_rowconfigure(11, weight=1)  # Treeview所在行
        for col in range(4):
            self.root.grid_columnconfigure(col, weight=1)  # 所有列等宽
        self.tree.bind("<Button-3>", self.copy_selection)

        self.time_label = tk.Label(self.root, text="计算耗时: 未计算")
        self.time_label.grid(row=12, column=0, columnspan=4, pady=5)

        self.toggle_inputs()
        self.toggle_precision()
    
    def toggle_inputs(self):
        mode = self.mode_var.get()
        self.x0_entry.config(state="normal" if mode == "s" else "disabled")
        self.start_entry.config(state="normal" if mode == "r" else "disabled")
        self.end_entry.config(state="normal" if mode == "r" else "disabled")
        self.step_entry.config(state="normal" if mode == "r" else "disabled")

    def toggle_precision(self):
        method = self.method_var.get()
        self.precision_entry.config(state="normal" if method == "sympy" else "disabled")

    def compute(self):
        try:
            expr = self.expr_entry.get()
            mode = self.mode_var.get()
            method = self.method_var.get()
            multi = self.multi_var.get()
            precision = int(self.precision_entry.get())
            digits = int(self.digits_entry.get())
            multi_bool = True if multi == "y" else False if multi == "n" else None

            if mode == "s":
                x0 = float(self.x0_entry.get())
                initial_values = [x0]
            else:
                start = float(self.start_entry.get())
                end = float(self.end_entry.get())
                step = float(self.step_entry.get())
                initial_values = [start + i * step for i in range(int((end - start) / step) + 1)]

            # 在线程中运行计算
            self.root.title("牛顿法求解器 - 计算中")
            self.tree.delete(*self.tree.get_children())
            self.time_label.config(text="计算耗时: 计算中...")
            thread = threading.Thread(target=self.run_computation, args=(expr, initial_values, method, multi_bool, precision, digits))
            thread.start()

        except ValueError as e:
            messagebox.showerror("错误", f"输入错误: {str(e)}")
            self.root.title("牛顿法求解器")

    def run_computation(self, expr: str, initial_values: List[float], method: str, multi: Optional[bool], precision: int, digits: int):
        try:
            roots, elapsed_time = self.solver_callback(expr, initial_values, method, multi, precision)
            self.root.after(0, self.update_results, expr, roots, elapsed_time, digits)
        except Exception as e:
            self.root.after(0, self.show_error, str(e))

    def update_results(self, expr: str, roots: List[Tuple[Optional[float], Optional[float], Optional[int]]], elapsed_time: float, digits: int):
        self.display_results(expr, roots, elapsed_time, digits)
        self.root.title("牛顿法求解器")

    def show_error(self, error_msg: str):
        messagebox.showerror("错误", f"计算失败: {error_msg}")
        self.time_label.config(text="计算耗时: 未完成")
        self.root.title("牛顿法求解器")

    def display_results(self, expr: str, roots: List[Tuple[Optional[float], Optional[float], Optional[int]]], elapsed_time: float, digits: int):
        self.tree.delete(*self.tree.get_children())
        for root, fx, it in roots:
            if root is not None:
                self.tree.insert("", "end", values=(f"{root:.{digits}f}", f"{fx:.{digits}e}", it))
        self.time_label.config(text=f"计算耗时: {elapsed_time:.3f} 秒")

    def copy_selection(self, event):
        selected_items = self.tree.selection()
        if selected_items:
            clipboard_text = ""
            for item in selected_items:
                values = self.tree.item(item, "values")
                clipboard_text += "\t".join(map(str, values)) + "\n"
            self.root.clipboard_clear()
            self.root.clipboard_append(clipboard_text.strip())
            self.root.update()

    def get_input(self, prompt: str, default: str = None) -> str:
        return default

    def run(self):
        self.root.mainloop()

    @staticmethod
    def is_available() -> bool:
        return True